package com.gezi.admin.module.code.gentable.table;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.CharsetUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gezi.admin.config.exception.type.ApiException;
import com.gezi.admin.module.code.gentable.table.model.CodeGenTableAddParamModel;
import com.gezi.admin.module.code.gentable.table.model.CodeGenTableSearchModel;
import com.gezi.admin.module.code.gentable.util.*;
import com.gezi.admin.module.code.gentable.column.CodeGenTableColumnEntity;
import com.gezi.admin.module.code.gentable.column.CodeGenTableColumnService;
import com.gezi.admin.module.code.gentable.column.model.CodeGenTableColumnAddParamModel;
import com.gezi.admin.module.code.gentable.table.model.CodeGenTableEditParamModel;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @Author 格子代码
 * @createDate 2024/2/10 09:00
 * @contact 1424529270@qq.com
 */
@Slf4j
@Service
public class CodeGenTableService extends ServiceImpl<CodeGenTableMapper, CodeGenTableEntity> {

    @Autowired
    private CodeGenTableColumnService codeGenTableColumnService;

    public Page<Map> findByPage(Page page, CodeGenTableSearchModel searchVo) {
        return baseMapper.findBySearchVo(page, searchVo);
    }

    public List<Map> findByList(CodeGenTableSearchModel searchVo) {
        return baseMapper.findBySearchVo(searchVo);
    }

    /**
     * 添加 - 事务
     *
     * @param paramModel
     */
    @Transactional(rollbackFor = Exception.class)
    public void add(CodeGenTableAddParamModel paramModel) {

    }

    @Transactional(rollbackFor = Exception.class)
    public void edit(CodeGenTableEditParamModel paramModel) {
        String options = JSON.toJSONString(paramModel.getParams());
        paramModel.setOptions(options);
        CodeGenTableEntity codeGenTableEntity = getById(paramModel.getId());
        codeGenTableEntity.setData(paramModel);
        saveOrUpdate(codeGenTableEntity);
        for (CodeGenTableColumnAddParamModel cenTableColumn : paramModel.getColumns()) {
            CodeGenTableColumnEntity codeGenTableColumnEntity = codeGenTableColumnService.getById(cenTableColumn.getId());
            codeGenTableColumnEntity.setData(cenTableColumn);
            codeGenTableColumnService.saveOrUpdate(codeGenTableColumnEntity);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void del(String id) {
        removeById(id);
        List<CodeGenTableColumnEntity> codeGenTableColumnEntityList = codeGenTableColumnService.findAllByGenTableId(id);
        codeGenTableColumnService.removeBatchByIds(codeGenTableColumnEntityList);
    }

    public Page<Map> dbFindAllByPage(Page page, CodeGenTableSearchModel searchVo) {
        return baseMapper.dbFindAllByPage(page, searchVo);
    }

    public List<CodeGenTableEntity> findDbTableListByNames(String[] tableNames) {
        return baseMapper.findDbTableListByNames(tableNames);
    }

    @Transactional(rollbackFor = Exception.class)
    public void importGenTable(List<CodeGenTableEntity> tableList) {
        for (CodeGenTableEntity table : tableList) {
            GenUtils.initTable(table);
            saveOrUpdate(table);
            // 保存列信息
            List<CodeGenTableColumnEntity> genTableColumns = codeGenTableColumnService.selectDbTableColumnsByName(table.getTableName());
            for (CodeGenTableColumnEntity column : genTableColumns) {
                GenUtils.initColumnField(column, table);
                codeGenTableColumnService.save(column);
            }
        }
    }

    /**
     * 预览代码
     *
     * @param id 表编号
     * @return 预览数据列表
     */
    public Map<String, String> previewCode(String id) {
        Map<String, String> dataMap = new LinkedHashMap<>();
        // 查询表信息
        CodeGenTableEntity table = getById(id);
        table.setColumns(codeGenTableColumnService.findAllByGenTableId(id));
        VelocityInitializer.initVelocity();

        VelocityContext context = VelocityUtils.prepareContext(table);

        // 获取模板列表
        List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
        for (String template : templates) {
            // 渲染模板
            StringWriter sw = new StringWriter();
            Template tpl = Velocity.getTemplate(template, Constants.UTF8);
            tpl.merge(context, sw);
            dataMap.put(template, sw.toString());
        }
        return dataMap;
    }

    /**
     * 生成代码（自定义路径）
     */
    public void generatorCode(String id)
    {
        CodeGenTableEntity table = getById(id);
        table.setColumns(codeGenTableColumnService.findAllByGenTableId(id));

        VelocityInitializer.initVelocity();

        VelocityContext context = VelocityUtils.prepareContext(table);

        // 获取模板列表
        List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
        for (String template : templates)
        {
            if (!StringUtils.containsAny(template,""))
            {
                // 渲染模板
                StringWriter sw = new StringWriter();
                Template tpl = Velocity.getTemplate(template, Constants.UTF8);
                tpl.merge(context, sw);
                try
                {
                    String path = getGenPath(table, template);
                    FileUtils.writeStringToFile(FileUtil.file(path), sw.toString(), CharsetUtil.UTF_8);
                }
                catch (IOException e)
                {
                    e.printStackTrace();
                    throw ApiException.getInstance("渲染模板失败，表名：" + table.getTableName());
                }
            }
        }
    }

    /**
     * 获取代码生成地址
     *
     * @param table    业务表信息
     * @param template 模板文件路径
     * @return 生成地址
     */
    public static String getGenPath(CodeGenTableEntity table, String template) {
        String genPath = table.getGenPath();
        if (StringUtils.equals(genPath, "/")) {
            if(template.contains("admin.vue.vm") || template.contains("api.js.vm") || template.contains("sql.vm")){
                return System.getProperty("user.dir") + File.separator + VelocityUtils.getFileName(template, table);
            }else{
                return System.getProperty("user.dir") + File.separator + "src" + File.separator + VelocityUtils.getFileName(template, table);
            }
        }
        return genPath + File.separator + VelocityUtils.getFileName(template, table);
    }

    public byte[] downloadCode(String id) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ZipOutputStream zip = new ZipOutputStream(outputStream);
        generatorCode(id, zip);
        IOUtils.closeQuietly(zip);
        return outputStream.toByteArray();
    }

    /**
     * 查询表信息并生成代码
     */
    private void generatorCode(String id, ZipOutputStream zip) {
        // 查询表信息
        CodeGenTableEntity table = getById(id);
        table.setColumns(codeGenTableColumnService.findAllByGenTableId(id));

        VelocityInitializer.initVelocity();

        VelocityContext context = VelocityUtils.prepareContext(table);

        // 获取模板列表
        List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
        for (String template : templates) {
            // 渲染模板
            StringWriter sw = new StringWriter();
            Template tpl = Velocity.getTemplate(template, Constants.UTF8);
            tpl.merge(context, sw);
            try {
                // 添加到zip
                zip.putNextEntry(new ZipEntry(VelocityUtils.getFileName(template, table)));
                IOUtils.write(sw.toString(), zip, Constants.UTF8);
                IOUtils.closeQuietly(sw);
                zip.flush();
                zip.closeEntry();
            } catch (IOException e) {
                e.printStackTrace();
                throw ApiException.getInstance("渲染模板失败，表名：" + table.getTableName());
            }
        }
    }

    /**
     * 同步通过表名
     * @param id
     */
    public void tongbuById(String id) {
        CodeGenTableEntity table = baseMapper.selectById(id);
        if(table == null){
            throw ApiException.getInstance("表不存在，请刷新重新");
        }
        table.setColumns(codeGenTableColumnService.findAllByGenTableId(table.getId()));
        List<CodeGenTableColumnEntity> tableColumns = table.getColumns();
        Map<String, CodeGenTableColumnEntity> tableColumnMap = tableColumns.stream().collect(Collectors.toMap(CodeGenTableColumnEntity::getColumnName, Function.identity()));

        List<CodeGenTableColumnEntity> dbTableColumns = codeGenTableColumnService.selectDbTableColumnsByName(table.getTableName());
        if (CollUtil.isEmpty(dbTableColumns)) {
            throw ApiException.getInstance("表不存在，请刷新重新");
        }
        List<String> dbTableColumnNames = dbTableColumns.stream().map(CodeGenTableColumnEntity::getColumnName).collect(Collectors.toList());

        List<CodeGenTableColumnEntity> saveColumns = new ArrayList<>();
        dbTableColumns.forEach(column -> {
            GenUtils.initColumnField(column, table);
            if (tableColumnMap.containsKey(column.getColumnName())) {
                CodeGenTableColumnEntity prevColumn = tableColumnMap.get(column.getColumnName());
                column.setId(prevColumn.getId());
                if (column.getIsQuery()) {
                    // 如果是列表，继续保留查询方式/字典类型选项
                    column.setDictType(prevColumn.getDictType());
                    column.setQueryType(prevColumn.getQueryType());
                }
                if (prevColumn.getIsRequired() && !column.isSuperColumn()
                        && (column.getIsInsert())
                        && ((column.isUsableColumn()) || (!column.isSuperColumn()))) {
                    // 如果是(新增/修改&非主键/非忽略及父属性)，继续保留必填/显示类型选项
                    column.setIsRequired(prevColumn.getIsRequired());
                    column.setHtmlType(prevColumn.getHtmlType());
                }
                codeGenTableColumnService.updateById(column);
            } else {
                codeGenTableColumnService.save(column);
            }
        });
        if (CollUtil.isNotEmpty(saveColumns)) {
            codeGenTableColumnService.updateBatchById(saveColumns);
        }

        List<CodeGenTableColumnEntity> delColumns = tableColumns.stream().filter(column -> !dbTableColumnNames.contains(column.getColumnName())).collect(Collectors.toList());
        if (CollUtil.isNotEmpty(delColumns)) {
            List<String> ids = delColumns.stream().map(CodeGenTableColumnEntity::getId).collect(Collectors.toList());
            codeGenTableColumnService.removeBatchByIds(ids);
        }
    }
}